using System.Collections.Generic;

#pragma warning disable IDE0090
/// <summary>
/// 
/// </summary>
public static class XEventManager
{
    public delegate bool TriggerEventCallback(params object[] args);

    public class XTiggerEvent
    {
        public int id;
        public TriggerEventCallback callback;
    }

    public class XTriggerListener
    {
        public int listenerId;

        public System.Enum eventId;
        public List<XTiggerEvent> events;
    }

    private static readonly Dictionary<System.Enum, XTriggerListener> listeners = new Dictionary<System.Enum, XTriggerListener>();

    /// <summary>
    /// 
    /// </summary>
    private static readonly Dictionary<int, XTriggerListener> 
        registerListeners = new Dictionary<int, XTriggerListener>();

    /// <summary>
    /// 
    /// </summary>
    public class XPendingEvent
    {
        public int id;
        public System.Enum eventId;
        public TriggerEventCallback callback;
    }
    private static readonly Dictionary<System.Enum, List<XPendingEvent>> registerPendings = new Dictionary<System.Enum, List<XPendingEvent>>();

    /// <summary>
    /// 
    /// </summary>
    private static readonly Dictionary<System.Enum, List<int>> 
        unregisterPendings = new Dictionary<System.Enum, List<int>>();

    /// <summary>
    /// 
    /// </summary>

    private static readonly Dictionary<System.Enum, bool> triggering = new Dictionary<System.Enum, bool>();


    /// <summary>
    /// 
    /// </summary>
    static int listenerId;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="eventId"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    public static bool Trigger(System.Enum eventId, params object[] args)
    {
        if (!listeners.ContainsKey(eventId))
            return false;

        XTriggerListener listener = listeners[eventId];
        if (!triggering.ContainsKey(eventId))
        {
            triggering.Add(eventId, true);
        }

        for (int i=0; i<listener.events.Count; i++)
        {
            XTiggerEvent trigger = listener.events[i];
            trigger.callback?.Invoke(args);
        }

        if (registerPendings.ContainsKey(eventId))
        {
            List<XPendingEvent> registerPending = registerPendings[eventId];
            for(int i=0; i< registerPending.Count; i++)
            {
                XPendingEvent pendingEvent = registerPending[i];
                DoRegister(listener, pendingEvent.id, pendingEvent.callback);
            }

            registerPending.Clear();
        }

        if (unregisterPendings.ContainsKey(eventId))
        {
            List<int> unregisterPending = unregisterPendings[eventId];
            for(int i=0; i< unregisterPending.Count; i++)
            {
                DoUnregister(listener, unregisterPending[i]);
            }

            unregisterPending.Clear();
        }

        triggering[eventId] = false;

        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="eventId"></param>
    /// <param name="tiggerCallback"></param>
    /// <returns></returns>
    public static int Register(System.Enum eventId, TriggerEventCallback triggerCallback)
    {
        int id = listenerId;
        listenerId ++;

        if (!listeners.ContainsKey(eventId))
        {
            listeners.Add(eventId, new XTriggerListener() {
                listenerId = id, eventId = eventId, events = new List<XTiggerEvent>()
            });
        }

        XTriggerListener listener = listeners[eventId];
        if (!registerListeners.ContainsKey(id))
        {
            registerListeners.Add(id, listener);
        }

        triggering.TryGetValue(eventId, out bool pending);
        if (pending)
        {
            if (!registerPendings.ContainsKey(eventId))
            {
                registerPendings.Add(eventId, new List<XPendingEvent>());
            }

            List<XPendingEvent> pendingList = registerPendings[eventId];
            pendingList.Add(new XPendingEvent(){
                eventId = eventId, id=id, callback = triggerCallback
            });
        }
        else
        {
            DoRegister(listener, id, triggerCallback);
        }
   
        return id;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="listener"></param>
    /// <param name="id"></param>
    /// <param name="tiggerCallback"></param>
    /// <returns></returns>
    static bool DoRegister(XTriggerListener listener, int id, TriggerEventCallback tiggerCallback)
    {
        listener.events.Add(new XTiggerEvent()
        {
            id = id,callback = tiggerCallback
        });

        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="listener"></param>
    /// <param name="id"></param>
    static void DoUnregister(XTriggerListener listener, int id)
    {
        for(int idx=0; idx<listener.events.Count; idx++)
        {
            XTiggerEvent triggerEvent = listener.events[idx];
            if (triggerEvent.id == id)
            {
                listener.events.RemoveAt(idx);
                break;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public static bool Unregister(int id)
    {
        if (!registerListeners.ContainsKey(id))
            return false;

        XTriggerListener registerlistener = registerListeners[id];
        triggering.TryGetValue(registerlistener.eventId, out bool trigger);
        if (trigger)
        {
            if (!unregisterPendings.ContainsKey(registerlistener.eventId))
            {
                unregisterPendings.Add(registerlistener.eventId, new List<int>());
            }

            List<int> pending = unregisterPendings[registerlistener.eventId];
            pending.Add(id);
        }
        else
        {
            DoUnregister(registerlistener, id);
        }
        
        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    public static void Reset()
    {
        registerPendings.Clear();
        unregisterPendings.Clear();
        registerListeners.Clear();
        
        triggering.Clear();

        listeners.Clear();
    }
}

#pragma warning restore IDE0090
